package indi.zhang.framework.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

/**
 * @author 痴
 * @className redisConfig
 * @since 2020-03-30 14:14
 */
@EnableCaching
public class RedisConfig {

	/**
	 * 往容器中添加RedisTemplate对象，设置序列化方式
	 *
	 * @param redisConnectionFactory
	 * @return
	 */
	@Bean
	public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
		RedisTemplate<String, Object> template = new RedisTemplate<>();
		template.setConnectionFactory(redisConnectionFactory);
		template.setKeySerializer(new StringRedisSerializer());
		template.setValueSerializer(valueSerializer());
		template.setHashKeySerializer(new StringRedisSerializer());
		template.setHashValueSerializer(valueSerializer());
		template.afterPropertiesSet();
		return template;

	}

	/**
	 * springboot cache KeyGenerator
	 *
	 * @return
	 */
	@Bean
	public KeyGenerator simpleKeyGenerator() {
		return (o, method, objects) -> {
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.append(o.getClass().getSimpleName());
			stringBuilder.append(".");
			stringBuilder.append(method.getName());
			stringBuilder.append("[");
			for (Object obj : objects) {
				stringBuilder.append(obj.toString());
			}
			stringBuilder.append("]");
			return stringBuilder.toString();
		};
	}

	@Bean
	public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
		return new RedisCacheManager(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
				//springboot 全局序列化方式 和缓存过期时间
				getRedisConfigurationWithTtl(600),
				//可针对不同的缓存需求 使用指定的 value 设置不同的缓存过期时间
				getRedisCacheConfigurationMap()
		);
	}

	/**
	 * 自定义缓存过期时间 使用方式 @Cacheable(value="自定义value",keyGenertor="simpleKeyGenerator")
	 *
	 * @return
	 */
	private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
		Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>(1);
		redisCacheConfigurationMap.put("自定义value", getRedisConfigurationWithTtl(100));
		return redisCacheConfigurationMap;
	}

	/**
	 * springboot cache 默认使用该序列化格式 和 缓存过期时间
	 *
	 * @param seconds 秒
	 * @return
	 */
	private RedisCacheConfiguration getRedisConfigurationWithTtl(Integer seconds) {
		RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
		redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
				RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer())
		).entryTtl(Duration.ofSeconds(seconds));
		return redisCacheConfiguration;
	}

	/**
	 * 使用Jackson序列化器
	 *
	 * @return
	 */
	private RedisSerializer<Object> valueSerializer() {
		Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
		ObjectMapper objectMapper = new ObjectMapper();
		objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
		objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
		serializer.setObjectMapper(objectMapper);
		return serializer;
	}

}
